/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package aimozg.algo.gtor;

/**
 *
 * @author aimozg
 */
public class Perlin {
    private long seed;
    private int octaves;
    private double persistence;
    private boolean exponentiation;

    public boolean hasExponentiation() {
        return exponentiation;
    }

    public void setExponentiation(boolean exponentiation) {
        this.exponentiation = exponentiation;
    }

    public int getOctaves() {
        return octaves;
    }

    public double getPersistence() {
        return persistence;
    }

    public void setOctaves(int octaves) {
        this.octaves = octaves;
    }

    public void setPersistence(double persistence) {
        this.persistence = persistence;
    }

    public Perlin(long seed, double persistence,int octaves) {
        this.seed = seed;
        this.octaves = octaves;
        this.persistence = persistence;
    }
    
    public Perlin(long seed){
        this(seed,1.0,1);
    }
    public Perlin(){
        this(System.nanoTime(),1.0,1);
    }
    public Perlin(double persistence,int octaves){
        this(System.nanoTime(),persistence,octaves);
    }
    
    public long next(long x){
        final long multiplier = 0x5DEECE66DL;
        final long addend = 0xBL;
        final long mask = (1L << 48) - 1;
        x = x+seed;
        return (x*multiplier + addend) & mask;
        //return ((x*0x5DEECE66DL)^((x<<17)+0xDEADBEEF))&((1L<<48)-1);
    }
    
    public double random1(long x){
        final long mask = (1L << 26)-1;
        long val1 = next(x)&mask;
        long val2 = next(val1)&mask;
        return (((long)(val1) << 27) + val2)/ (double)(1L << 53);
    }
    public double random2(long x, long y){
        long i = x+y+x*y;
        return random1(i);
    }
    public double random3(long x, long y, long z){
        long i = x+y+z+x*y+y*z+x*z;
        return random1(i);
    }
    public double random4(long x, long y, long z, long w){
        long i = x+y+z+w+x*y+x*z+x*w+y*x+y*w+z*w;
        return random1(i);
    }
    public static double lint(double y0, double y1, double x){
        return y0*x+y1*(1-x);
    }
    public double random1(double x){
        long x0 = (long)x;
        double xf = 1+x0-x;
        return lint(random1(x0),random1(x0+1),xf);
    }
    public double random2(double x,double y){
        long x0 = (long)x;
        double xf = 1+x0-x;
        long y0 = (long)y;
        double yf = 1+y0-y;
        double v_y0 = lint(random2(x0,y0),random2(x0+1,y0),xf);
        double v_y1 = lint(random2(x0,y0+1),random2(x0+1,y0+1),xf);
        return lint(v_y0,v_y1,yf);
    }
    public double random3(double x, double y, double z){
        long x0 = (long)x;
        double xf = 1+x0-x;
        long y0 = (long)y;
        double yf = 1+y0-y;
        long z0 = (long)z;
        double zf = 1+z0-z;
        double v_y0z0 = lint(random3(x0,y0,z0),random3(x0+1,y0,z0),xf);
        double v_y0z1 = lint(random3(x0,y0,z0+1),random3(x0+1,y0,z0+1),xf);
        double v_y1z0 = lint(random3(x0,y0+1,z0),random3(x0+1,y0+1,z0),xf);
        double v_y1z1 = lint(random3(x0,y0+1,z0+1),random3(x0+1,y0+1,z0+1),xf);
        double v_z0 = lint(v_y0z0,v_y1z0,yf);
        double v_z1 = lint(v_y0z1,v_y1z1,yf);
        return lint(v_z0,v_z1,zf);
    }
    public double random4(double x, double y, double z, double w){
        long x0 = (long)x;
        double xf = 1+x0-x;
        long y0 = (long)y;
        double yf = 1+y0-y;
        long z0 = (long)z;
        double zf = 1+z0-z;
        long w0 = (long)w;
        double wf = 1+w0-w;
        double v_y0z0w0 = lint(random4(x0,y0,z0,w0),random4(x0+1,y0,z0,w0),xf);
        double v_y0z0w1 = lint(random4(x0,y0,z0,w0+1),random4(x0+1,y0,z0,w0+1),xf);
        double v_y0z1w0 = lint(random4(x0,y0,z0+1,w0),random4(x0+1,y0,z0+1,w0),xf);
        double v_y0z1w1 = lint(random4(x0,y0,z0+1,w0+1),random4(x0+1,y0,z0+1,w0+1),xf);
        double v_y1z0w0 = lint(random4(x0,y0+1,z0,w0),random4(x0+1,y0+1,z0,w0),xf);
        double v_y1z0w1 = lint(random4(x0,y0+1,z0,w0+1),random4(x0+1,y0+1,z0,w0+1),xf);
        double v_y1z1w0 = lint(random4(x0,y0+1,z0+1,w0),random4(x0+1,y0+1,z0+1,w0),xf);
        double v_y1z1w1 = lint(random4(x0,y0+1,z0+1,w0+1),random4(x0+1,y0+1,z0+1,w0+1),xf);
        double v_z0w0 = lint(v_y0z0w0,v_y1z0w0,yf);
        double v_z0w1 = lint(v_y0z0w1,v_y1z0w1,yf);
        double v_z1w0 = lint(v_y0z1w0,v_y1z1w0,yf);
        double v_z1w1 = lint(v_y0z1w1,v_y1z1w1,yf);
        double v_w0 = lint(v_z0w0,v_z1w0,zf);
        double v_w1 = lint(v_z0w1,v_z1w1,zf);
        return lint(v_w0,v_w1,wf);
    }
    
    private double join(double[] vals){
        double val = 0;
        double q = 0;
        for (int i = 0; i<octaves; i++){
            val = val*persistence+vals[i];
            q = q*persistence+1;
        }
        if (exponentiation){
            return (Math.exp(val/q)-(Math.E-1));
        } else {
            return val/q;
        }
    }
    
    public double noise1(double x){
        double[] vals = new double[octaves];
        for (int i = octaves-1; i>=0; i--){
            vals[i] = random1(x/(1L<<i));
        }
        return join(vals);
    }
    public double noise2(double x,double y){
        double[] vals = new double[octaves];
        for (int i = octaves-1; i>=0; i--){
            vals[i] = random2(
                    x/(1L<<i),
                    y/(1L<<i));
        }
        return join(vals);
    }
    public double noise3(double x,double y,double z){
        double[] vals = new double[octaves];
        for (int i = octaves-1; i>=0; i--){
            vals[i] = random3(
                    x/(1L<<i),
                    y/(1L<<i),
                    z/(1L<<i));
        }
        return join(vals);
    }
    public double noise4(double x,double y,double z,double w){
        double[] vals = new double[octaves];
        for (int i = octaves-1; i>=0; i--){
            vals[i] = random4(
                    x/(1L<<i),
                    y/(1L<<i),
                    z/(1L<<i),
                    w/(1L<<i));
        }
        return join(vals);
    }
}
